const PROMISE_STATUS = {
  PENDING: 'PENDING',
  RESOLVED: 'RESOLVED',
  REJECTED: 'REJECTED',
}

const PROMISE_HANDLER_TYPE = {
  THEN: 'THEN',
  CATCH: 'CATCH',
  FINALLY: 'FINALLY'
}

type HandlerFunc = (resolve: Function, reject: Function) => any
type ThenFunc = (data: any) => any
type CatchFunc = (error: any) => any

class MyPromise {
  private status: string
  private value: any
  private handlers: Array<{
    type: string
    handler: Function
  }>
  private result: {
    type: string
    result: any
  }
  private isRun: boolean
  constructor(handler: HandlerFunc) {
    if (typeof handler !== 'function') {
      throw Error('then argument must be Function')
    }
    this.status = PROMISE_STATUS.PENDING
    this.handlers = []
    try {
      handler(this.resolve.bind(this), this.reject.bind(this))
    } catch (error) {
      this.reject(error)
    }
  }
  private resolve(result) {
    if (this.status !== PROMISE_STATUS.PENDING) {
      return
    }
    this.status = PROMISE_STATUS.RESOLVED
    this.result = {
      type: PROMISE_HANDLER_TYPE.THEN,
      result,
    }
    this.value = result
    this.checkRun()
  }
  private reject(result) {
    if (this.status !== PROMISE_STATUS.PENDING) {
      return
    }
    this.status = PROMISE_STATUS.REJECTED
    this.result = {
      type: PROMISE_HANDLER_TYPE.CATCH,
      result,
    }
    this.value = result
    this.checkRun()
  }
  private checkRun() {
    if (this.isRun || this.status === PROMISE_STATUS.PENDING) {
      return
    }
    this.isRun = true
    setTimeout(() => this.run(), 0)
  }
  private run() {
    let { type: resultType, result } = this.result
    while (this.handlers.length > 0) {
      const { type: handlerType, handler } = this.handlers.shift()
      if (
        handlerType === PROMISE_HANDLER_TYPE.FINALLY ||
        handlerType === resultType
      ) {
        try {
          let nResult = handler(result)
          if (handlerType !== PROMISE_HANDLER_TYPE.FINALLY) {
            resultType = PROMISE_HANDLER_TYPE.THEN
            result = nResult
          }
        } catch (error) {
          result = error
          resultType = PROMISE_HANDLER_TYPE.CATCH
        }
      } 
    }
    this.result = { type: resultType, result }
    this.isRun = false
  }
  public then(thenFunc: ThenFunc, catchFunc?: CatchFunc): MyPromise {
    this.handlers.push({
      type: PROMISE_HANDLER_TYPE.THEN,
      handler,
    })
    if (catchFunc) {
      return this.catch(catchFunc)
    } else {
      this.checkRun()
      return this
    }
  }
  public catch(handler: CatchFunc): MyPromise {
    this.handlers.push({
      type: PROMISE_HANDLER_TYPE.CATCH,
      handler,
    })
    this.checkRun()
    return this
  }
  public finally(handler: Function): MyPromise {
    this.handlers.push({
      type: PROMISE_HANDLER_TYPE.FINALLY,
      handler,
    })
    this.checkRun()
    return this
  }
  public static all(promises: Array<MyPromise>) {
    return new MyPromise((resolve, reject) => {
      const results = []
      promises.forEach(p => {
        p.then(val => {
          results.push(val)
          if (results.length === promises.length) {
            resolve(results)
          }
        }).catch(reject)
      })
    })
  }
  public static race(promises: Array<MyPromise>) {
    return new MyPromise((resolve, reject) => {
      const results = []
      let isCallback = false
      promises.forEach(p => {
        p.then(val => {
          if (!isCallback) {
            isCallback = true
            resolve(val)
          }
        }).catch(reject)
      })
    })
  }
  public static reject(result: any) {
    return new MyPromise((resolve, reject) => {
      reject(result)
    })
  }
  public static resolve(result: any) {
    return new MyPromise((resolve, reject) => {
      resolve(result)
    })
  }
}